package com.sj.controller;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.sj.common.bean.LocalUser;
import com.sj.common.dto.article.ArticleDto;
import com.sj.common.dto.article.ArticleQueryParam;
import com.sj.common.dto.article.ArticleSaveParam;
import com.sj.common.dto.comment.CommentDto;
import com.sj.common.dto.user.UserDto;
import com.sj.common.exception.CustomException;
import com.sj.common.lang.ApiResult;
import com.sj.common.lang.Const;
import com.sj.common.lang.RedisConst;
import com.sj.common.lang.ResultInfo;
import com.sj.entity.Article;
import com.sj.entity.Comment;
import com.sj.entity.Label;
import com.sj.entity.User;
import com.sj.interceptor.AuthCheck;
import com.sj.service.*;
import com.sj.utils.MineUtils;
import com.sj.utils.RedisUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.annotation.security.RolesAllowed;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * <p>
 * 文章表 前端控制器
 * </p>
 *
 * @author 孙峻
 * @since 2022-03-09
 */
@RestController
@RequestMapping("/article")
@Api(tags = "文章模块")
public class ArticleController {

    @Autowired
    private ArticleService articleService;

    @Autowired
    private LabelService labelService;

    @Autowired
    private PraiseService praiseService;

    @Autowired
    private CommentService commentService;

    @Autowired
    private UserService userService;

    @Autowired
    private CollectService collectService;

    @Autowired
    private MineUtils mineUtils;

    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private FootprintService footprintService;

    @ApiOperation("管理端删除文章")
    @DeleteMapping("/admin/del/{id}")
    @AuthCheck
    @CacheEvict(cacheNames = {"article-num-data", "article-list", "fix-type"}, keyGenerator = RedisConst.REDIS_SJ_KEY_GENERATOR, allEntries = true)
    public ApiResult delInAdminArticle(@PathVariable Integer id) {
        boolean result = this.articleService.removeById(id);
        if (result) {
            return ApiResult.success("删除成功");
        }
        return ApiResult.error("删除失败");
    }

    @PostMapping("/admin/list")
    @ApiOperation("获取管理端文章列表")
    @AuthCheck
    @Cacheable(cacheNames = "article-list", keyGenerator = RedisConst.REDIS_SJ_KEY_GENERATOR)
    public ApiResult getAdminArticleList(@RequestBody ArticleQueryParam articleQueryParam) {
        IPage<Article> page = new Page<>(articleQueryParam.getCurrent(), articleQueryParam.getSize());
        return ApiResult.success(this.article2Dto(articleService.getAdminArticleList(articleQueryParam, page)));
    }

    @DeleteMapping("/del/{id}")
    @ApiOperation("删除文章接口")
    @AuthCheck
    @CacheEvict(cacheNames = {"article-num-data", "article-list", "fix-type"}, keyGenerator = RedisConst.REDIS_SJ_KEY_GENERATOR, allEntries = true)
    public ApiResult delArticle(@PathVariable Integer id) {
        boolean result = articleService.remove(Wrappers.<Article>lambdaQuery().eq(Article::getId, id).eq(Article::getUserId, LocalUser.getUser().getId()));
        if (result) {
            return ApiResult.success("删除成功");
        }
        return ApiResult.error("删除失败");
    }

    @ApiOperation("搜索接口")
    @GetMapping("/search/{keyword}/{current}/{size}")
    @Cacheable(cacheNames = "article-list", keyGenerator = RedisConst.REDIS_SJ_KEY_GENERATOR)
    public ApiResult search(@PathVariable String keyword, @PathVariable Integer current, @PathVariable Integer size) {
        IPage<Article> page = new Page<>(current, size);
        if (!keyword.startsWith(Const.SEARCH_KEYWORD_PREFIX)) {
            return ApiResult.success("搜索关键字格式错误");
        }
        int index = keyword.indexOf(Const.COLON);
        if (index == -1) {
            return ApiResult.success("搜索关键字格式错误");
        }
        keyword = keyword.substring(index + 1);
        IPage<Article> pageInfo = articleService.lambdaQuery().like(Article::getTitle, keyword).or().like(Article::getContent, keyword).page(page);
        return ApiResult.success(this.article2Dto(pageInfo));
    }

    @GetMapping("/list/user/{uId}/{current}/{size}/{type}")
    @ApiOperation("获取指定用户的文章")
    @Cacheable(cacheNames = "article-list", keyGenerator = RedisConst.REDIS_SJ_KEY_GENERATOR)
    public ApiResult getUserArticleList(@PathVariable String uId, @PathVariable Integer current, @PathVariable Integer size, @PathVariable String type) {
        if (!uId.startsWith(Const.USER_ID_PREFIX)) {
            return ApiResult.error("用户id信息错误");
        }
        int index = uId.indexOf(Const.COLON);
        if (index == -1) {
            return ApiResult.error("用户id信息错误");
        }
        Integer uid = null;
        try {
            uid = Integer.parseInt(uId.substring(index + 1));
        } catch (NumberFormatException e) {
            return ApiResult.error("用户id信息错误");
        }
        IPage<Article> page = new Page<>(current, size);
        IPage<Article> pageInfo = articleService.lambdaQuery().eq(Article::getUserId, uid).eq(Article::getType, type).orderBy(true, false, Article::getUpdateTime).page(page);
        return ApiResult.success(this.article2Dto(pageInfo));
    }

    /**
     * 获取指定标签的文章列表
     *
     * @param tag     标签信息，有指定的前缀，如果前缀不符合直接返回错误信息，这样也解决了 redis相同key的问题
     * @param current
     * @param size
     * @param type    文章类型，如果为article就是查所有文章
     * @return
     */
    @GetMapping("/fix/tag")
    @ApiOperation("获取指定标签的文章列表")
    @Cacheable(cacheNames = "article-list", keyGenerator = RedisConst.REDIS_SJ_KEY_GENERATOR)
    public ApiResult getFixTagArticle(@RequestParam("tag") String[] tag, @RequestParam("current") Integer current, @RequestParam("size") Integer size, @RequestParam("type") String type) {
        if (tag.length == 0) {
            return ApiResult.error("标签信息错误");
        }

        int index = 0;

        ArrayList<String> trueTag = new ArrayList<String>();
        for (String temp : tag) {
            if (!temp.startsWith(Const.TAG_PREFIX)) {
                return ApiResult.error("标签信息错误");
            }
            // 根据标签名 找到拥有该标签的文章
            // 找到第一个冒号 从第一个冒号开始分割
            index = temp.indexOf(Const.COLON);
            trueTag.add(temp.substring(index + 1));
        }
        IPage<Article> page = new Page<>(current, size);

        return ApiResult.success(this.article2Dto(articleService.getFixTagArticle(trueTag, type, page)));
    }

    @GetMapping("/fix/type")
    @AuthCheck
    @ApiOperation("获取指定类型的文章列表")
    @Cacheable(cacheNames = "fix-type", keyGenerator = RedisConst.REDIS_SJ_KEY_GENERATOR)
    public ApiResult getFixTypeArticle(@RequestParam("type") String type, @RequestParam("current") Integer current, @RequestParam("size") Integer size) {
        User user = LocalUser.getUser();
        IPage<Article> articlePage = new Page<>(current, size);
        switch (type) {
            case Const.ANSWER_NUM:
                IPage<Comment> answerPage = new Page<>(current, size);
                IPage<Comment> page = commentService.getUserAnswer(answerPage,user.getId());
                IPage<CommentDto> resultPage = new Page<>();
                BeanUtil.copyProperties(page, resultPage, "records");
                resultPage.setRecords(page.getRecords().stream().map(temp -> {
                    CommentDto commentDto = new CommentDto();
                    BeanUtil.copyProperties(temp, commentDto, "parent", "user", "childList", "praiseNum", "article");
                    commentDto.setPraiseNum(praiseService.getSumPraiseNum(commentDto.getId(), Const.ANSWER));
                    commentDto.setArticle(articleService.getById(commentDto.getArticleId()));
                    commentDto.setUser(userService.getUserDetailInfo(articleService.getById(commentDto.getArticleId()).getUserId()));
                    return commentDto;
                }).collect(Collectors.toList()));
                return ApiResult.success(resultPage);
            case Const.ATTENTION_QUESTION_NUM:
                return ApiResult.success(this.article2Dto(collectService.getFixArticleTypeList(user, Const.ARTICLE_QUESTION, articlePage)));
            case Const.BLOG_NUM:
                return ApiResult.success(this.article2Dto(articleService.getFixTypeArticleByPage(user, Const.ARTICLE_BLOG, articlePage)));
            case Const.COLLECT_NUM:
                return ApiResult.success(this.article2Dto(collectService.getCollectArticleList(user, articlePage)));
            case Const.IDEA_NUM:
                return ApiResult.success(this.article2Dto(articleService.getFixTypeArticleByPage(user, Const.ARTICLE_IDEA, articlePage)));
            case Const.QUESTION_NUM:
                return ApiResult.success(this.article2Dto(articleService.getFixTypeArticleByPage(user, Const.ARTICLE_QUESTION, articlePage)));
            case Const.FOOTPRINT_NUM:
                return ApiResult.success(this.article2Dto(footprintService.getFootPrintArticleByPage(user, articlePage)));
            default:
                break;
        }
        return ApiResult.error("出现未知错误");
    }


    @PutMapping("/add/browse/{articleId}")
    @ApiOperation("添加浏览次数")
    public ApiResult addBrowse(@PathVariable Integer articleId) {
        // 首先判断redis有没有该文章的浏览次数记录 如果没有 先从数据库获取 然后 + 1 再存到redis 如果有就直接添加
        Long browseNum = 0L;
        if (!redisUtils.hHasKey(Const.ARTICLE, RedisConst.REDIS_BROWSE + Const.COLON + articleId)) {
            // 没有
            Article tempArticle = articleService.getById(articleId);
            // 现在只存了从数据库中获取的次数 并没有加一
            redisUtils.hset(Const.ARTICLE, RedisConst.REDIS_BROWSE + Const.COLON + articleId, tempArticle.getBrowse());
        }
        // 有
        browseNum = new Double(String.valueOf(redisUtils.hincr(Const.ARTICLE, RedisConst.REDIS_BROWSE + Const.COLON + articleId, 1))).longValue();
        // 当浏览次数为一百的倍数时，更新数据库
        if (browseNum % RedisConst.UPDATE_BROWSE_VALUE == 0) {
            articleService.lambdaUpdate().setSql(true, "browse = " + browseNum).eq(Article::getId, articleId).update();
        }
        return ApiResult.success("添加浏览次数成功", browseNum);
    }

    @PutMapping("/save")
    @ApiOperation("发布文章")
    @AuthCheck
    @CacheEvict(cacheNames = {"article-list", "article-num-data", "all-label-name", "fix-type", "article-detail"}, keyGenerator = RedisConst.REDIS_SJ_KEY_GENERATOR, allEntries = true)
    public ApiResult saveArticle(@RequestBody @Validated ArticleSaveParam articleSaveParam) {
        Article article = new Article();
        BeanUtil.copyProperties(articleSaveParam, article, "tags");
        User user = LocalUser.getUser();
        article.setUserId(user.getId());
        boolean result = articleService.saveArticle(article, articleSaveParam.getTags());

        if (result) {
            return ApiResult.success("保存成功", MapUtil.builder()
                    .put("id", article.getId())
                    .build());
        }
        return ApiResult.error(ResultInfo.ERROR);
    }

    @ApiOperation("获取文章列表")
    @GetMapping("/list/{type}/{current}/{size}")
    @Cacheable(cacheNames = "article-list", keyGenerator = RedisConst.REDIS_SJ_KEY_GENERATOR)
    public ApiResult getArticleList(@PathVariable String type, @PathVariable Integer current, @PathVariable Integer size) {
        IPage<Article> pageInfo = new Page<>(current, size);
        IPage<Article> page = null;
        if (Const.RECOMMEND_ARTICLE.equals(type)) {
            Integer uid = null;
            try {
                uid = mineUtils.getUidByToken();
            } catch (Exception e) {
                uid = null;
            }
            page = articleService.getRecommendList(Const.ARTICLE, uid, pageInfo);
        } else if (Const.HOT_ARTICLE.equals(type)) {
            page = articleService.getHotArticleList(pageInfo, Const.ARTICLE);
        } else if (Const.ATTENTION_AUTHOR_ARTICLE.equals(type)) {
            Integer uid = mineUtils.getUidByToken();
            page = articleService.getAttentionArticleList(pageInfo, uid);
        } else if (Const.MINE_ARTICLE.equals(type)) {
            Integer uid = mineUtils.getUidByToken();
            page = articleService.lambdaQuery().eq(Article::getUserId, uid).orderBy(true, false, Article::getUpdateTime, Article::getCreateTime).page(pageInfo);
        }
        return ApiResult.success(this.article2Dto(page));
    }

    /**
     * 获取指定类型的文章列表推荐
     * 如果 type = article 就是所有类型的文章
     * 如果 type = question , idea , blog 就是指定类型的
     * <p>
     * 推荐规则
     * 根据用户点赞文章数量多的标签在前 少的在后
     * <p>
     * flag 参数 没有实质性的意义，但是可以作为缓存名 多一个参数也就不会重复了
     *
     * @return
     */
    @ApiOperation("获取推荐列表")
    @GetMapping("/recommend/{type}/{current}/{size}/{flag}")
    @Cacheable(cacheNames = "article-list", keyGenerator = RedisConst.REDIS_SJ_KEY_GENERATOR)
    public ApiResult getRecommendArticleList(@PathVariable String type, @PathVariable Integer current, @PathVariable Integer size, @PathVariable Boolean flag) {
        // 如果已登录，则传用户id 没有传null
        Integer uid = null;
        if (flag) {
            try {
                uid = mineUtils.getUidByToken();
            } catch (CustomException e) {
                uid = null;
            }
        }
        IPage<Article> page = new Page<>(current, size);
        return ApiResult.success(this.article2Dto(articleService.getRecommendList(type, uid, page)));
    }

    /**
     * 热门规定
     * 按照点赞，浏览量 更新时间，创建时间排序
     * <p>
     * 注意这里的cacheNames 和 文章列表的cacheNames的值是一样的，原因：这两个方法不可能同时参数相同，但是他们需要清除缓存的条件是一样的，这样更方便
     *
     * @param size
     * @param type idea question blog
     * @return
     */
    @ApiOperation("获取热门固定类型文章列表")
    @GetMapping("/hot/list/{current}/{size}/{type}")
    @Cacheable(cacheNames = {"article-list"}, keyGenerator = RedisConst.REDIS_SJ_KEY_GENERATOR)
    public ApiResult getHotArticleList(@PathVariable Integer current, @PathVariable Integer size, @PathVariable String type) {
        IPage<Article> page = new Page<>(current, size);
        return ApiResult.success(this.article2Dto(articleService.getHotArticleList(page, type)));
    }

    /**
     * 关于cacheNames的值，与上同理 因为参数位置不同，所以也不会有相同的情况
     *
     * @param type idea question blog
     * @param size
     * @return
     */
    @ApiOperation("获取指定文章类型的最新文章")
    @GetMapping("/new/list/{current}/{size}/{type}")
    @Cacheable(cacheNames = {"article-list"}, keyGenerator = RedisConst.REDIS_SJ_KEY_GENERATOR)
    public ApiResult getNewArticleListDiffType(@PathVariable Integer current, @PathVariable String type, @PathVariable Integer size) {
        IPage<Article> page = new Page<>(current, size);
        return ApiResult.success(this.article2Dto(articleService.lambdaQuery().eq(Article::getType, type).page(page)));
    }

    @ApiOperation("获取文章详情")
    @GetMapping("/detail/{id}")
    @Cacheable(cacheNames = "article-detail", keyGenerator = RedisConst.REDIS_SJ_KEY_GENERATOR)
    public ApiResult getArticleDetail(@PathVariable Integer id) {
        Article article = articleService.lambdaQuery().eq(Article::getId, id).one();
        return ApiResult.success(this.article2Dto(article));
    }

    /**
     * 带分页器的文章类型转换
     *
     * @param page
     * @return
     */
    private IPage<ArticleDto> article2Dto(IPage<Article> page) {
        IPage<ArticleDto> resultPage = new Page<>();
        BeanUtil.copyProperties(page, resultPage, "records");
        resultPage.setRecords(page.getRecords().stream().map(temp -> this.article2Dto(temp)).collect(Collectors.toList()));
        return resultPage;
    }

    /**
     * 将 article表映射对象转换为dto对象
     *
     * @param article
     * @return
     */
    private ArticleDto article2Dto(Article article) {
        ArticleDto articleDto = new ArticleDto();
        BeanUtil.copyProperties(article, articleDto, "labelList", "praiseNum", "commentNum", "isCollect", "user");
        // 标签列表
        List<Label> list = labelService.lambdaQuery().inSql(Label::getId, "select label_id from article_label where del = 0 and article_id = " + articleDto.getId()).list();
        articleDto.setLabelList(list);

        // 点赞数
        Long praiseNum = praiseService.getSumPraiseNum(articleDto.getId(), Const.ARTICLE);
        articleDto.setPraiseNum(praiseNum);

        // 用户信息
        UserDto user = userService.getUserDetailInfo(article.getUserId());
        articleDto.setUser(user);

        Integer uid = null;
        try {
            uid = mineUtils.getUidByToken();
            if (uid != null) {
                // 是否收藏
                Boolean isCollect = collectService.checkIsCollect(uid, articleDto.getId(), Const.ARTICLE);
                articleDto.setIsCollect(isCollect);
                // 是否关注作者
                articleDto.setUserIsAttention(collectService.checkIsCollect(uid, user.getId(), Const.AUTHOR));
            }

        } catch (CustomException e) {
            System.out.println("用户还未登录，不执行需要用户id的方法");
        }

        // 评论数
        Long commentNum = commentService.getSumCommentNum(articleDto.getId(), Const.COMMENT);
        articleDto.setCommentNum(commentNum);

        // 收藏数量
        Long collectNum = collectService.getCollectNum(articleDto.getId(), Const.ARTICLE);
        articleDto.setCollectNum(collectNum);

        return articleDto;
    }

}
